﻿
package extremefx.modules {
	import extremefx.events.MessageEventArgs;	
	import extremefx.events.ProgressEventArgs;	
	import extremefx.events.ObjectEventArgs;	
	import extremefx.IDisposable;	
	
	import flash.display.DisplayObjectContainer;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.geom.Rectangle;
	import flash.media.Video;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.utils.Timer;
	
	import extremefx.TimeSpan;
	import extremefx.events.EventHandler;
	import extremefx.modules.BasePlayer;		

	/**
	 * @author Marcelo Volmaro
	 */
	public class VideoPlayer extends BasePlayer implements IDisposable {
		private var _connection:NetConnection;
		private var _stream:NetStream;
		private var _video:Video;
		private var _checkVideo:Timer;
		private var _width:uint;
		private var _height:uint;
		private var _mwidth:uint;
		private var _mheight:uint;
		private var _x:int;
		private var _y:int;
		private var _duration:TimeSpan;
		private var _keyTimes:Array;
		private var _keyPositions:Array;
		private var _h264:Boolean;
		private var _timeoffset:Number;
		private var _offset:Number;
		private var _loaded:Number;
		private var _state:uint;
		
		public function VideoPlayer() {
			super();
			_video = new Video();
			_bufferLength = 5000;
			_checkVideo = new Timer(100);
			_connection = new NetConnection();
            _connection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
            _connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, ioErrorHandler);
            _connection.connect(null);
            _width = _height = 0;
            _duration = new TimeSpan(0);
            
            var s:NetStream = _stream = new NetStream(_connection);
			s.soundTransform = _soundTransform;
			s.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
			
			s.client = {
        		onMetaData:_onMetaData,
        		onCuePoint:_onCuePoint
        	};
        	
        	_checkVideo.addEventListener(TimerEvent.TIMER, _timeHandler, false, 0, true);
		}
		
		public function setBounds(pX:int, pY:int, pW:uint = 0, pH:uint = 0):void {
			_x = pX || 0;
			_y = pY || 0;
			
			_width = pW;
			_height = pH;
			
			if (pW == 0 && _mwidth) pW = _mwidth;
			if (pH == 0 && _mheight) pH = _mheight;
			
			_video.x = _x;
			_video.y = _y;
			_video.height = pH;
			_video.width = pW;
		}
		
		private var _disposing:Boolean;

		public function getBounds(pBounds:Rectangle = null):Rectangle {
			if (!(pBounds is Rectangle)) {
				pBounds = new Rectangle();
			}
			
			pBounds.left = _x;
			pBounds.top = _y;
			pBounds.width = _width || _mwidth;
			pBounds.height = _height || _mheight;
			
			return pBounds;
		}
		
		private function netStatusHandler(event:Object):void {
			trace("netStatusHandler");
//			trace(ObjectDump.dump(event as Object));
			trace(event.info.code);
			trace("\n\n");
			
			//handles NetConnection and NetStream status events
			switch (event.info.code) {
					
				case "NetConnection.Connect.Success":
					if (_eOnOpen) _eOnOpen.fire();
					//play stream if connection successful
//					connectStream();
					break;
					
				case "NetStream.Play.StreamNotFound":
					//error if stream file not found in
					//location specified
					trace("Stream not found: " + _url);
					break;
					
				case "NetStream.Play.Stop":
					//stream.bytesLoaded == stream.bytesTotal => complete?? verificar
					//do if video is stopped
//					videoPlayComplete();
					break;
					
				//"NetStream.Buffer.Empty"
//"NetStream.Buffer.Full"
//"NetStream.Play.Start" "status" Playback has started. 
//"NetStream.Play.Stop" "status" Playback has stopped. 
//"NetStream.Pause.Notify"
//"NetStream.Unpause.Notify"
//"NetStream.Seek.Notify"

				case "NetStream.Play.Complete":
					//do if video play completes
					videoComplete();
					break;
			}
			
			if (_eOnConnectionStatus) _eOnConnectionStatus.fire(event.info.code);
		}
		
		public function setUrl(pU:String):void {
			if (_url == pU) return;
		
			_url = pU;
			_firstPlay = true;
		
			if (_isPlaying) {
				stop();
				_stream = null;
			}
		}
		
		public function get position():TimeSpan{
	    	return new TimeSpan(_stream.time * 1000 || 0);
		}
		
		public function set position(pP:TimeSpan):void{
    		_position = Math.round(pP.ticks/1000);
    		_seek(_position);
		}
		
		private function _seek(pPos:Number):void{
			var pos:Number = _getOffset(pPos);
    		
    		if (pos < _offset || pos > _offset + _loaded) {
                _offset = pos;
                _timeoffset = _getOffset(pPos, true);
                _stream.play(_url + "?start="+ (_h264 ? _timeoffset : _offset));
                
            } else {
                if (_h264) {
                    _stream.seek(pPos - _timeoffset);
                    
                } else {
                    _stream.seek(pPos);
                }
            }

        	if (_isPlaying) {
            	play();
        	}
		}
		
		public function get duration():TimeSpan {
			return _duration;
		}

		public function set bufferLength(pValue:Number): void { 
			_bufferLength = pValue;
			_stream.bufferTime = pValue;
		}
		
		public function attachToDisplay(pDisplayObject:DisplayObjectContainer):void {
			pDisplayObject.addChild(_video);
		}

		public function removeFromDisplay():void {
			if (_video.parent is DisplayObjectContainer) {
				_video.parent.removeChild(_video);
			}
		}

		public function play():void {
			if (_url && _url != ""){
				if (_firstPlay){
					_position = _loaded = _offset = 
					_mwidth = _mheight = _state = _timeoffset = 0;
					
					_checkVideo.addEventListener(TimerEvent.TIMER, _checkVideoSize, false, 0, true);
					_checkVideo.stop();
                	_checkVideo.start();
					
					_keyTimes = [];
					_keyPositions = [];
					_stream.play(_url);
					_video.attachNetStream(_stream);
					_firstPlay = false;
					
					if (_eOnOpen) _eOnOpen.fire();
				}
				
				_stream.resume();
				_isPlaying = true;
			}
		}
		
		private function _getOffset(pForTime:Number, pGetIndex:Boolean = false):Number {
            var index:uint = 0, l:uint = _keyTimes.length, k:Array = _keyTimes; 

            while (index++ < l) {

                if (k[index] <= pForTime && k[index + 1] >= pForTime || !k[index + 1]) {
                	return pGetIndex ? k[index] : _keyPositions[index];
                }
            }
            
            return 0;
        }
		
		
        private function _onMetaData(pMetadata:Object):void{
        	_metadata = pMetadata;
 
        	if (pMetadata.width) {
        		_mwidth = uint(pMetadata.width);
        		if (_width == 0) _video.width = _mwidth;
        	}
        	
        	if (pMetadata.height) {
        		_mheight = uint(pMetadata.height);
        		if (_height == 0 ) _video.height = _mheight;
        	}

        	if (pMetadata.duration) _duration = new TimeSpan(pMetadata.duration * 1000);
        	if (pMetadata.seekpoints){
        		var sp:* = pMetadata.seekpoints; 
        		_h264 = true;
        		for (var i:String in sp) {
                    _keyTimes[uint(i)] = Number(sp[i]["time"]);
                    _keyPositions[uint(i)] = Number(sp[i]["offset"]);
                }
                
        	} else if (pMetadata.keyframes){
        		_keyPositions = pMetadata.keyframes;
        	}
        	
        	if (_stream.time < _position) _seek(_position);
        	//_stream.seek(_position);//user seek to position, but no metadata was available...
        	
        	if (_eOnMetaData) _eOnMetaData.fire(new ObjectEventArgs(_metadata));
        }
		
        private function _onCuePoint(pCuePoint:Object):void{
        	if (_eOnCuePoint) _eOnCuePoint.fire(new ObjectEventArgs(pCuePoint));
        }
		
		private function _checkVideoSize(e:TimerEvent):void{
			_loaded = _stream.bytesLoaded;
			var p:ProgressEventArgs = new ProgressEventArgs(_loaded, _stream.bytesTotal);
			
			if (isNaN(p.percent)) return;
			
        	if(p.percent == 1 && _stream.bytesTotal > 8){
        		_checkVideo.removeEventListener(TimerEvent.TIMER, _checkVideoSize);
                completeHandler(new Event(Event.COMPLETE));
                
            } else if(_stream.bytesTotal == 0 && _stream.bytesTotal > 4){
            	
                
            } else {
            	if (_eOnProgress) _eOnProgress.fire(p);
            }
        }
        
        private function _timeHandler(e:TimerEvent):void {
        	if (!_isPlaying) return;
        	
            var abuffer:Number = _stream.bufferLength / _stream.bufferTime;
            var cpos:Number = Math.round(_stream.time * 10) / 10;
            
            if (_h264) {
                cpos = cpos + _timeoffset;
            }
            
            var vlength:Number = Math.round(_duration.ticks/1000);
            
            if (abuffer < 1 && cpos < Math.abs(vlength - _stream.bufferTime * 2)) {
//                model.sendEvent(ModelEvent.BUFFER, {percentage:abuffer});
                
                if (_state != 1 && abuffer < 0.50) {
                	_state = 1;
//                    model.sendEvent(ModelEvent.STATE, {newstate:ModelStates.BUFFERING});
                }
                
            } else if (_state == 1) {
            	_state = 0;
//                model.sendEvent(ModelEvent.STATE, {newstate:ModelStates.PLAYING});
            }
            
//            if (vlength > 0) {
//                model.sendEvent(ModelEvent.TIME, {position:cpos, duration:vlength});
//            }
            
            return;
        }
		
		private function ioErrorHandler(errorEvent:IOErrorEvent):void {
			if (_eOnError) _eOnError.fire(new MessageEventArgs(errorEvent.text));	
		}

		private function completeHandler(pEvent:Event):void {
			if (_eOnComplete) _eOnComplete.fire();
		}
		
		public function get metadata():Object {
			return _metadata;
		}
	
		private function videoComplete():void{
			if (_eOnPlayComplete) _eOnPlayComplete.fire();
			
			stop();
			if (_loop) play();
		}
		
		public function pause():void {
			_position = _stream.time;
			_stream.pause();
			_isPlaying = false;
		}
		
		public function stop():void {
			_position = _timeoffset = _offset = 0;
			
			if (_stream.bytesLoaded != _stream.bytesTotal) {
                _stream.close();
            }
            
            _stream.pause();
			_isPlaying = false;
		}
		
		public function destroy():void {
			stop();
			_stream = null;
		}
		
		public function set volume(volume:Number):void {
			_volume = volume;
			_soundTransform.volume = volume;
		}
		
		public function set useHighQuality(pQ:Boolean):void{
			if (pQ) {
                _video.smoothing = true;
                _video.deblocking = 5;//3, 4 or 5
                
            } else {
                _video.smoothing = false;
                _video.deblocking = 1;
            }
		}
		
		private var _eOnCuePoint:EventHandler;
		public function get onCuePoint():EventHandler{
			if (_eOnCuePoint == null) _eOnCuePoint = new EventHandler(this);
			return _eOnCuePoint;
		}
		
		private var _eOnConnectionStatus:EventHandler;
		public function get onConnectionStatus():EventHandler{
			if (_eOnConnectionStatus == null) _eOnConnectionStatus = new EventHandler(this);
			return _eOnConnectionStatus;
		}
		
		public function dispose():void {
			if (_disposing) return;
			_disposing = true;
			
			_stream.close();
			_connection.close();
			
			_checkVideo.stop();
			_checkVideo.removeEventListener(TimerEvent.TIMER, _checkVideoSize);
			_checkVideo.removeEventListener(TimerEvent.TIMER, _timeHandler);
			
			removeFromDisplay();
		}
	}
}
